package mornd.test3;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.stereotype.Component;

/**
 * @author: mornd
 * @dateTime: 2023/5/5 - 0:22
 */
public class ComponentScanPostProcessor
        implements BeanFactoryPostProcessor, ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if(!(beanFactory instanceof DefaultListableBeanFactory)) {
            return;
        }
        DefaultListableBeanFactory dlBeanFactory = (DefaultListableBeanFactory) beanFactory;

        try {
            ComponentScan componentScan =
                    AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);

            // 生成 @component 注解所在的类的 bean 的名称
            AnnotationBeanNameGenerator nameGenerator = new AnnotationBeanNameGenerator();
            if(componentScan != null) {
                String[] packages = componentScan.basePackages();
                for (String p : packages) {
                    String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";

                    // new PathMatchingResourcePatternResolver().getResources(path); // 可替代 applicationContext 解析 resource 功能
                    Resource[] resources = applicationContext.getResources(path);
                    CachingMetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
                    for (Resource r : resources) {
                        //System.out.println(r);

                        MetadataReader mr = readerFactory.getMetadataReader(r);
                        // 类名
                        String className = mr.getClassMetadata().getClassName();

                        // 注解信息
                        AnnotationMetadata annoMetadata = mr.getAnnotationMetadata();

                        boolean b = annoMetadata.hasAnnotation(Component.class.getName());
                        boolean b1 = annoMetadata.hasMetaAnnotation(Component.class.getName());
                        // 派生注解例如：@Controller
                        System.out.println("类"+ className + "是否包含@Component：" + b +
                                "，是否包含@Component或其派生注解：" + b1);

                        if(b || b1) {
                            AbstractBeanDefinition beanDefinition =
                                    BeanDefinitionBuilder.genericBeanDefinition(className).getBeanDefinition();

                            // 生成名称 mornd.test3.Config ==> config
                            String name = nameGenerator.generateBeanName(beanDefinition, dlBeanFactory);
                            // 添加 bean 定义到容器中
                            dlBeanFactory.registerBeanDefinition(name, beanDefinition);
                        }
                    }
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
